<!DOCTYPE html>



  


<html class="theme-next gemini use-motion" lang="zh-Hans">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css" />


  <link rel="apple-touch-icon" sizes="180x180" href="/%E5%8D%9A%E5%AE%A2.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/%E5%8D%9A%E5%AE%A2.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/%E5%8D%9A%E5%AE%A2.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="Mybatis," />





  <link rel="alternate" href="/atom.xml" title="Createsequence's Blog" type="application/atom+xml" />






<meta name="description" content="概述 根据上一篇文章，我们了解了 Mybatis 如何在加载配置文件后，根据指定配置方式寻找接口并且完成映射文件信息与接口的绑定的。在本篇文章，我们将延续上文，结合源码阐述映射文件中方法声明里的 sql 被解析为 java 对象的过程。 这是关于 Mybatis 的第三篇文章，前文：  Mybatis源码学习（一）：配置文件的加载 Mybatis源码学习（二）：Mapper接口的绑定  一、sql">
<meta property="og:type" content="article">
<meta property="og:title" content="Mybatis源码学习（三）：映射文件中sql的解析">
<meta property="og:url" content="http://blog.xiajibagao.top/2021/07/11/mybatis/Mybatis%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%B8%89%EF%BC%89%EF%BC%9A%E6%98%A0%E5%B0%84%E6%96%87%E4%BB%B6%E4%B8%ADsql%E7%9A%84%E8%A7%A3%E6%9E%90/index.html">
<meta property="og:site_name" content="Createsequence&#39;s Blog">
<meta property="og:description" content="概述 根据上一篇文章，我们了解了 Mybatis 如何在加载配置文件后，根据指定配置方式寻找接口并且完成映射文件信息与接口的绑定的。在本篇文章，我们将延续上文，结合源码阐述映射文件中方法声明里的 sql 被解析为 java 对象的过程。 这是关于 Mybatis 的第三篇文章，前文：  Mybatis源码学习（一）：配置文件的加载 Mybatis源码学习（二）：Mapper接口的绑定  一、sql">
<meta property="og:locale">
<meta property="og:image" content="http://img.xiajibagao.top/image-20210711012449848.png">
<meta property="og:image" content="http://img.xiajibagao.top/image-20210622225424211.png">
<meta property="og:image" content="http://img.xiajibagao.top/image-20210622235212852.png">
<meta property="article:published_time" content="2021-07-10T16:00:00.000Z">
<meta property="article:modified_time" content="2021-07-11T02:10:35.267Z">
<meta property="article:author" content="Createsequence">
<meta property="article:tag" content="Mybatis">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="http://img.xiajibagao.top/image-20210711012449848.png">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '',
    scheme: 'Gemini',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":true,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="http://blog.xiajibagao.top/2021/07/11/mybatis/Mybatis源码学习（三）：映射文件中sql的解析/"/>





  <title>Mybatis源码学习（三）：映射文件中sql的解析 | Createsequence's Blog</title>
  








<meta name="generator" content="Hexo 5.2.0"></head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">Createsequence's Blog</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle">一个努力前进的程序猿</p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br />
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
            
            归档
          </a>
        </li>
      

      
        <li class="menu-item menu-item-search">
          
            <a href="javascript:;" class="popup-trigger">
          
            
              <i class="menu-item-icon fa fa-search fa-fw"></i> <br />
            
            搜索
          </a>
        </li>
      
    </ul>
  

  
    <div class="site-search">
      
  <div class="popup search-popup local-search-popup">
  <div class="local-search-header clearfix">
    <span class="search-icon">
      <i class="fa fa-search"></i>
    </span>
    <span class="popup-btn-close">
      <i class="fa fa-times-circle"></i>
    </span>
    <div class="local-search-input-wrapper">
      <input autocomplete="off"
             placeholder="搜索..." spellcheck="false"
             type="text" id="local-search-input">
    </div>
  </div>
  <div id="local-search-result"></div>
</div>



    </div>
  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://blog.xiajibagao.top/2021/07/11/mybatis/Mybatis%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%B8%89%EF%BC%89%EF%BC%9A%E6%98%A0%E5%B0%84%E6%96%87%E4%BB%B6%E4%B8%ADsql%E7%9A%84%E8%A7%A3%E6%9E%90/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/Createsequence.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Createsequence's Blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">Mybatis源码学习（三）：映射文件中sql的解析</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2021-07-11T00:00:00+08:00">
                2021-07-11
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Mybatis/" itemprop="url" rel="index">
                    <span itemprop="name">Mybatis</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  4.2k
                </span>
              

              

              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <h2 id="概述">概述</h2>
<p>根据上一篇文章，我们了解了 Mybatis 如何在加载配置文件后，根据指定配置方式寻找接口并且完成映射文件信息与接口的绑定的。在本篇文章，我们将延续上文，结合源码阐述映射文件中方法声明里的 sql 被解析为 java 对象的过程。</p>
<p>这是关于 Mybatis 的第三篇文章，前文：</p>
<blockquote>
<p><a href="https://blog.xiajibagao.top/2021/06/05/mybatis/Mybatis%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9A%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E7%9A%84%E5%8A%A0%E8%BD%BD/">Mybatis源码学习（一）：配置文件的加载</a></p>
<p><a href="https://blog.xiajibagao.top/2021/06/14/mybatis/Mybatis%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AMapper%E6%8E%A5%E5%8F%A3%E7%9A%84%E7%BB%91%E5%AE%9A/">Mybatis源码学习（二）：Mapper接口的绑定</a></p>
</blockquote>
<h2 id="一-sql解析">一、sql解析</h2>
<p><img src="http://img.xiajibagao.top/image-20210711012449848.png" alt="image-20210711012449848"></p>
<h3 id="1xmlmapperbuilder">1.XMLMapperBuilder</h3>
<p>Sql 解析与 <code>MappedStatement</code>的生成都在 <code>XMLMapperBuilder</code> 进行。根据上文可知，在 <code>XMLMapperBuilder</code> 的 <code>parsePendingStatements()</code>方法如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">parsePendingStatements</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 获取所有映射文件中的方法声明</span></span><br><span class="line">    Collection&lt;XMLStatementBuilder&gt; incompleteStatements = configuration.getIncompleteStatements();</span><br><span class="line">    <span class="keyword">synchronized</span> (incompleteStatements) &#123;</span><br><span class="line">        Iterator&lt;XMLStatementBuilder&gt; iter = incompleteStatements.iterator();</span><br><span class="line">        <span class="keyword">while</span> (iter.hasNext()) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 遍历并转换为Statement对象</span></span><br><span class="line">                iter.next().parseStatementNode();</span><br><span class="line">                iter.remove();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IncompleteElementException e) &#123;</span><br><span class="line">                <span class="comment">// Statement is still missing a resource...</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>其中，<code>Configuration</code>类中的<code>IncompleteStatements</code>是在<code>XMLMapperBuilder.parse()</code>时添加进去的，我们可以理解他是一个刚从配置文件中根据<code>&lt;select&gt;&lt;delete&gt;&lt;update&gt;&lt;insert&gt;</code>标签名拿到的 XML 节点，还没有做任何解析。</p>
<p>等到完成了接口与映射文件信息的绑定以后，再遍映射文件中的方法声明依次解析。</p>
<h3 id="2parsestatementnode方法">2.parseStatementNode方法</h3>
<p>这个方法比较长，但是主要作用就是解析一个方法声明中的属性与子标签：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">parseStatementNode</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 获取id属性</span></span><br><span class="line">    String id = context.getStringAttribute(<span class="string">&quot;id&quot;</span>);</span><br><span class="line">    String databaseId = context.getStringAttribute(<span class="string">&quot;databaseId&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!databaseIdMatchesCurrent(id, databaseId, <span class="keyword">this</span>.requiredDatabaseId)) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取标签节点名称</span></span><br><span class="line">    String nodeName = context.getNode().getNodeName();</span><br><span class="line">    <span class="comment">// 判断sql类型</span></span><br><span class="line">    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));</span><br><span class="line">    <span class="comment">// 如果是select就判断是否需要获取/清空缓存</span></span><br><span class="line">    <span class="keyword">boolean</span> isSelect = sqlCommandType == SqlCommandType.SELECT;</span><br><span class="line">    <span class="keyword">boolean</span> flushCache = context.getBooleanAttribute(<span class="string">&quot;flushCache&quot;</span>, !isSelect);</span><br><span class="line">    <span class="keyword">boolean</span> useCache = context.getBooleanAttribute(<span class="string">&quot;useCache&quot;</span>, isSelect);</span><br><span class="line">    <span class="keyword">boolean</span> resultOrdered = context.getBooleanAttribute(<span class="string">&quot;resultOrdered&quot;</span>, <span class="keyword">false</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 将include节点转为相应的sql节点</span></span><br><span class="line">    <span class="comment">// Include Fragments before parsing</span></span><br><span class="line">    XMLIncludeTransformer includeParser = <span class="keyword">new</span> XMLIncludeTransformer(configuration, builderAssistant);</span><br><span class="line">    includeParser.applyIncludes(context.getNode());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取入参类型</span></span><br><span class="line">    String parameterType = context.getStringAttribute(<span class="string">&quot;parameterType&quot;</span>);</span><br><span class="line">    Class&lt;?&gt; parameterTypeClass = resolveClass(parameterType);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取lang</span></span><br><span class="line">    String lang = context.getStringAttribute(<span class="string">&quot;lang&quot;</span>);</span><br><span class="line">    LanguageDriver langDriver = getLanguageDriver(lang);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取selectKey节点，解析完成后删除</span></span><br><span class="line">    <span class="comment">// Parse selectKey after includes and remove them.</span></span><br><span class="line">    processSelectKeyNodes(id, parameterTypeClass, langDriver);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 解析sql</span></span><br><span class="line">    <span class="comment">// Parse the SQL (pre: &lt;selectKey&gt; and &lt;include&gt; were parsed and removed)</span></span><br><span class="line">    KeyGenerator keyGenerator; <span class="comment">// 生成主键</span></span><br><span class="line">    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;</span><br><span class="line">    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, <span class="keyword">true</span>);</span><br><span class="line">    <span class="comment">// 如果设置了selectKey，则在插入获取主键生成器生成的主键</span></span><br><span class="line">    <span class="keyword">if</span> (configuration.hasKeyGenerator(keyStatementId)) &#123;</span><br><span class="line">        keyGenerator = configuration.getKeyGenerator(keyStatementId);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        keyGenerator = context.getBooleanAttribute(<span class="string">&quot;useGeneratedKeys&quot;</span>,</span><br><span class="line">                                                   configuration.isUseGeneratedKeys() &amp;&amp; SqlCommandType.INSERT.equals(sqlCommandType))</span><br><span class="line">            ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建对应的SqlSource对象</span></span><br><span class="line">    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);</span><br><span class="line">    <span class="comment">// 获取Statement类型，默认为prepared</span></span><br><span class="line">    StatementType statementType = StatementType.valueOf(context.getStringAttribute(<span class="string">&quot;statementType&quot;</span>, StatementType.PREPARED.toString()));</span><br><span class="line">    <span class="comment">// 获取一些连接参数</span></span><br><span class="line">    Integer fetchSize = context.getIntAttribute(<span class="string">&quot;fetchSize&quot;</span>);</span><br><span class="line">    Integer timeout = context.getIntAttribute(<span class="string">&quot;timeout&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 入参和返回值类型</span></span><br><span class="line">    String parameterMap = context.getStringAttribute(<span class="string">&quot;parameterMap&quot;</span>);</span><br><span class="line">    String resultType = context.getStringAttribute(<span class="string">&quot;resultType&quot;</span>);</span><br><span class="line">    Class&lt;?&gt; resultTypeClass = resolveClass(resultType);</span><br><span class="line">    String resultMap = context.getStringAttribute(<span class="string">&quot;resultMap&quot;</span>);</span><br><span class="line">    String resultSetType = context.getStringAttribute(<span class="string">&quot;resultSetType&quot;</span>);</span><br><span class="line">    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);</span><br><span class="line">    <span class="keyword">if</span> (resultSetTypeEnum == <span class="keyword">null</span>) &#123;</span><br><span class="line">        resultSetTypeEnum = configuration.getDefaultResultSetType();</span><br><span class="line">    &#125;</span><br><span class="line">    String keyProperty = context.getStringAttribute(<span class="string">&quot;keyProperty&quot;</span>);</span><br><span class="line">    String keyColumn = context.getStringAttribute(<span class="string">&quot;keyColumn&quot;</span>);</span><br><span class="line">    String resultSets = context.getStringAttribute(<span class="string">&quot;resultSets&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 构建MappedStatement并添加到配置类</span></span><br><span class="line">    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,</span><br><span class="line">                                        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,</span><br><span class="line">                                        resultSetTypeEnum, flushCache, useCache, resultOrdered,</span><br><span class="line">                                        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="3sqlsource">3.SqlSource</h3>
<p>仍然还是在上文的 <code>parseStatementNode()</code>方法中，暂且忽略缓存与各种返回值类型的处理，我们关注一下<code>addMappedStatement()</code>中传入的变量 <code>sqlSource</code> ，它本身是一个接口，跟<code>DataSource</code>的功能一样，通过<code>SqlSource</code>接口的实现类，可以获取 sql 对象：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">SqlSource</span> </span>&#123;</span><br><span class="line">    <span class="function">BoundSql <span class="title">getBoundSql</span><span class="params">(Object parameterObject)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>他具有的唯一一个抽象方法即为<code>getBoundSql()</code>，这个方法获取的 <code>BoundSql</code>类就是实际上的我们认为的方法声明中的那个 sql 对象：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BoundSql</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String sql;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> List&lt;ParameterMapping&gt; parameterMappings;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Object parameterObject;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, Object&gt; additionalParameters;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> MetaObject metaParameters;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>里面包含有带占位符和动态标签的原始 sql 语句，以及方法声明上相关的入参。</p>
<h3 id="4languagedriver">4.LanguageDriver</h3>
<p>LanguageDriver 本身也是一个接口，他的实现类用于解析参数以及注解和映射文件中的 sql，并最终根据此生成 sql 数据源，我们可以简单的理解为针对指定格式 sql 语句的解析器：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">LanguageDriver</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 创建一个参数处理器，将处理完参数后得到实际参数传递给JDBC语句。</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">    <span class="function">ParameterHandler <span class="title">createParameterHandler</span><span class="params">(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql)</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 解析XML并创建SqlSource</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">    <span class="function">SqlSource <span class="title">createSqlSource</span><span class="params">(Configuration configuration, XNode script, Class&lt;?&gt; parameterType)</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 解析注解并创建SqlSource</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">    <span class="function">SqlSource <span class="title">createSqlSource</span><span class="params">(Configuration configuration, String script, Class&lt;?&gt; parameterType)</span></span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><code>SqlSource</code> 通过 <code>LanguageDriver.createSqlSource()</code>创建：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">String lang = context.getStringAttribute(<span class="string">&quot;lang&quot;</span>);</span><br><span class="line">LanguageDriver langDriver = getLanguageDriver(lang);</span><br><span class="line">... ...</span><br><span class="line">SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);</span><br></pre></td></tr></table></figure>
<p>而这里的<code>getLanguageDriver()</code>方法，最终会回到<code>Configuration.getLanguageDriver()</code>中：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (langClass == <span class="keyword">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> languageRegistry.getDefaultDriver();</span><br><span class="line">&#125;</span><br><span class="line">languageRegistry.register(langClass);</span><br><span class="line"><span class="keyword">return</span> languageRegistry.getDriver(langClass);</span><br></pre></td></tr></table></figure>
<p>而映射文件里方法声明中 <code>lang</code> 属性我们一般不会刻意设置，按上述逻辑，获取的是默认的语言驱动，这里的语言驱动来自于 <code>Configuration</code> 初始化时候注册的 <code>XMLLanguageDriver</code>和 <code>RawLanguageDriver</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">Configuration</span><span class="params">()</span> </span>&#123;</span><br><span class="line">	... ...</span><br><span class="line">    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);</span><br><span class="line">    languageRegistry.register(RawLanguageDriver.class);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="5xmllanguagedriver">5.XMLLanguageDriver</h3>
<p><code>XMLLanguageDriver</code> 是<code>LanguageDriver</code>接口的实现，它用于解析 xml 格式的 sql 语句——或者更准确点说，是 mybatis 特定的方法声明语法。</p>
<p><code>RawLanguageDriver</code>继承了<code>XMLLanguageDriver</code>，而<code>XMLLanguageDriver</code>又实现了<code>LanguageDriver</code>接口：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">XMLLanguageDriver</span> <span class="keyword">implements</span> <span class="title">LanguageDriver</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建参数处理器</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> ParameterHandler <span class="title">createParameterHandler</span><span class="params">(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> DefaultParameterHandler(mappedStatement, parameterObject, boundSql);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 解析映射文件的sql</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> SqlSource <span class="title">createSqlSource</span><span class="params">(Configuration configuration, XNode script, Class&lt;?&gt; parameterType)</span> </span>&#123;</span><br><span class="line">        XMLScriptBuilder builder = <span class="keyword">new</span> XMLScriptBuilder(configuration, script, parameterType);</span><br><span class="line">        <span class="keyword">return</span> builder.parseScriptNode();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 解析有复杂参数的sql</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> SqlSource <span class="title">createSqlSource</span><span class="params">(Configuration configuration, String script, Class&lt;?&gt; parameterType)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// issue #3</span></span><br><span class="line">        <span class="comment">// 解析使用@Select这类sql标签上的sql</span></span><br><span class="line">        <span class="keyword">if</span> (script.startsWith(<span class="string">&quot;&lt;script&gt;&quot;</span>)) &#123;</span><br><span class="line">            XPathParser parser = <span class="keyword">new</span> XPathParser(script, <span class="keyword">false</span>, configuration.getVariables(), <span class="keyword">new</span> XMLMapperEntityResolver());</span><br><span class="line">            <span class="comment">// 先转为xml再按照映射文件的方式解析</span></span><br><span class="line">            <span class="keyword">return</span> createSqlSource(configuration, parser.evalNode(<span class="string">&quot;/script&quot;</span>), parameterType);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// issue #127</span></span><br><span class="line">			<span class="comment">// 替换占位符中的变量，转为sql语法节点</span></span><br><span class="line">            script = PropertyParser.parse(script, configuration.getVariables());</span><br><span class="line">            TextSqlNode textSqlNode = <span class="keyword">new</span> TextSqlNode(script);</span><br><span class="line">            <span class="comment">// 如果含有动态标签</span></span><br><span class="line">            <span class="keyword">if</span> (textSqlNode.isDynamic()) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> DynamicSqlSource(configuration, textSqlNode);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 不含动态标签</span></span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> RawSqlSource(configuration, script, parameterType);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>基于<code>XMLLanguageDriver</code>，<code>RawLanguageDriver</code>进一步完善了方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RawLanguageDriver</span> <span class="keyword">extends</span> <span class="title">XMLLanguageDriver</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> SqlSource <span class="title">createSqlSource</span><span class="params">(Configuration configuration, XNode script, Class&lt;?&gt; parameterType)</span> </span>&#123;</span><br><span class="line">        SqlSource source = <span class="keyword">super</span>.createSqlSource(configuration, script, parameterType);</span><br><span class="line">        checkIsNotDynamic(source);</span><br><span class="line">        <span class="keyword">return</span> source;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> SqlSource <span class="title">createSqlSource</span><span class="params">(Configuration configuration, String script, Class&lt;?&gt; parameterType)</span> </span>&#123;</span><br><span class="line">        SqlSource source = <span class="keyword">super</span>.createSqlSource(configuration, script, parameterType);</span><br><span class="line">        checkIsNotDynamic(source);</span><br><span class="line">        <span class="keyword">return</span> source;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">checkIsNotDynamic</span><span class="params">(SqlSource source)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!RawSqlSource.class.equals(source.getClass())) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> BuilderException(<span class="string">&quot;Dynamic content is not allowed when using RAW language&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>主要是再做检查，<strong>防止非静态的 sql 使用 RawSqlSource</strong>。</p>
<p><strong>对于 Myabtis 来说，sql 中带有 <code>$&#123;&#125;</code>和<code>&lt;where&gt;</code>这类动态标签的都认为是动态 sql，反之则是静态 sql</strong></p>
<h3 id="7xmlscriptbuilder">7.XMLScriptBuilder</h3>
<p><code>XMLScriptBuilder</code>这个类用于对 Mybatis 的映射文件中的方法声明里的动态标签做解析。它的内部有一个 <code>NodeHandle</code>接口实现类集合，用于处理专门的动态标签：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">initNodeHandlerMap</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    nodeHandlerMap.put(<span class="string">&quot;trim&quot;</span>, <span class="keyword">new</span> TrimHandler());</span><br><span class="line">    nodeHandlerMap.put(<span class="string">&quot;where&quot;</span>, <span class="keyword">new</span> WhereHandler());</span><br><span class="line">    nodeHandlerMap.put(<span class="string">&quot;set&quot;</span>, <span class="keyword">new</span> SetHandler());</span><br><span class="line">    nodeHandlerMap.put(<span class="string">&quot;foreach&quot;</span>, <span class="keyword">new</span> ForEachHandler());</span><br><span class="line">    nodeHandlerMap.put(<span class="string">&quot;if&quot;</span>, <span class="keyword">new</span> IfHandler());</span><br><span class="line">    nodeHandlerMap.put(<span class="string">&quot;choose&quot;</span>, <span class="keyword">new</span> ChooseHandler());</span><br><span class="line">    nodeHandlerMap.put(<span class="string">&quot;when&quot;</span>, <span class="keyword">new</span> IfHandler());</span><br><span class="line">    nodeHandlerMap.put(<span class="string">&quot;otherwise&quot;</span>, <span class="keyword">new</span> OtherwiseHandler());</span><br><span class="line">    nodeHandlerMap.put(<span class="string">&quot;bind&quot;</span>, <span class="keyword">new</span> BindHandler());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> SqlSource <span class="title">parseScriptNode</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 解析动态标签</span></span><br><span class="line">    MixedSqlNode rootSqlNode = parseDynamicTags(context);</span><br><span class="line">    SqlSource sqlSource;</span><br><span class="line">    <span class="keyword">if</span> (isDynamic) &#123;</span><br><span class="line">        <span class="comment">// 是否带有动态标签的非静态sql</span></span><br><span class="line">        sqlSource = <span class="keyword">new</span> DynamicSqlSource(configuration, rootSqlNode);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 静态sql</span></span><br><span class="line">        sqlSource = <span class="keyword">new</span> RawSqlSource(configuration, rootSqlNode, parameterType);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> sqlSource;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">protected</span> MixedSqlNode <span class="title">parseDynamicTags</span><span class="params">(XNode node)</span> </span>&#123;</span><br><span class="line">    List&lt;SqlNode&gt; contents = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line">    <span class="comment">// 解析方法声明中的节点</span></span><br><span class="line">    NodeList children = node.getNode().getChildNodes();</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; children.getLength(); i++) &#123;</span><br><span class="line">        XNode child = node.newXNode(children.item(i));</span><br><span class="line">        <span class="keyword">if</span> (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) &#123;</span><br><span class="line">            <span class="comment">// 获取sql语句</span></span><br><span class="line">            String data = child.getStringBody(<span class="string">&quot;&quot;</span>);</span><br><span class="line">            TextSqlNode textSqlNode = <span class="keyword">new</span> TextSqlNode(data);</span><br><span class="line">            <span class="comment">// 是带有$&#123;&#125;的动态sql</span></span><br><span class="line">            <span class="keyword">if</span> (textSqlNode.isDynamic()) &#123;</span><br><span class="line">                contents.add(textSqlNode);</span><br><span class="line">                isDynamic = <span class="keyword">true</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 是静态sql</span></span><br><span class="line">                contents.add(<span class="keyword">new</span> StaticTextSqlNode(data));</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (child.getNode().getNodeType() == Node.ELEMENT_NODE) &#123; <span class="comment">// issue #628</span></span><br><span class="line">            <span class="comment">// sql中带有动态标签</span></span><br><span class="line">            String nodeName = child.getNode().getNodeName();</span><br><span class="line">            <span class="comment">// 获取对应的拦截器</span></span><br><span class="line">            NodeHandler handler = nodeHandlerMap.get(nodeName);</span><br><span class="line">            <span class="keyword">if</span> (handler == <span class="keyword">null</span>) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> BuilderException(<span class="string">&quot;Unknown element &lt;&quot;</span> + nodeName + <span class="string">&quot;&gt; in SQL statement.&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            handler.handleNode(child, contents);</span><br><span class="line">            isDynamic = <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> MixedSqlNode(contents);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<h3 id="8sqlnode">8.SqlNode</h3>
<p><code>parseDynamicTags()</code>最终返回的 <code>MixedSqlNode</code> 是一个 SqlNode 实现类，SqlNode 是一个表示节点的特殊接口：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">SqlNode</span> </span>&#123;</span><br><span class="line">  <span class="function"><span class="keyword">boolean</span> <span class="title">apply</span><span class="params">(DynamicContext context)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>他的方法及其简单，每个 SqlNode 实现类都会实现<code>apply()</code>方法，当调用以后，sql 节点会被解析为正常的 sql 语句放入 <code>DynamicContext</code>上下文中。</p>
<p>所有方法声明中的 sql 节点都实现类这个接口：</p>
<p><img src="http://img.xiajibagao.top/image-20210622225424211.png" alt="image-20210622225424211"></p>
<p>简而言之，他们的实现类很多，但是实际上就分四类：</p>
<ol>
<li>StaticTextSqlNode：顾名思义，普通的静态 sql 语句部分，只带有<code>#&#123;&#125;</code>表达式而不含有动态标签和<code>$&#123;&#125;</code>赋值表达式；</li>
<li>TextSqlNode：带有<code>$&#123;&#125;</code>赋值表达式的 sql 语句；</li>
<li>动态标签节点：除了<code>TextSqlNode</code>、<code>MixedSqlNode</code>和<code>StaticTextSqlNode</code>的所有其他实现类，即处理 Mybatis 动态标签的特殊节点；</li>
<li>MixedSqlNode：如<code>parseDynamicTags()</code>所示，这是一个混合了所有类型标签的节点，也是实际上的根节点；</li>
</ol>
<p>我们以 <code>StaticTextSqlNode</code>为例：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">StaticTextSqlNode</span> <span class="keyword">implements</span> <span class="title">SqlNode</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String text;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">StaticTextSqlNode</span><span class="params">(String text)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.text = text;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">apply</span><span class="params">(DynamicContext context)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 拼接sql</span></span><br><span class="line">        context.appendSql(text);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>普通静态 sql 节点 <code>apply()</code>以后就把当前的节点内的 sql 拼接到上下文的 sql 中，同理，其他的实现类也差不多。由于节点之间还有嵌套关系，因此有些节点还会自己维护一个独立的上下文，内部处理的时候先把 sql 拼到独立上下文里面，等自己的节点处理完再把独立上下文中的 sql 拼接到父目录的上下文中。</p>
<p><img src="http://img.xiajibagao.top/image-20210622235212852.png" alt="image-20210622235212852"></p>
<p>根据各自的逻辑处理后，最终都会把节点代表的 sql 拼接到上下文里，最终所有节点逻辑处理完，就会在上下文中拼接处一条完整的 sql。</p>
<p>而起到这个作用的，就是根节点 <code>MixedSqlNode</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MixedSqlNode</span> <span class="keyword">implements</span> <span class="title">SqlNode</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> List&lt;SqlNode&gt; contents;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">MixedSqlNode</span><span class="params">(List&lt;SqlNode&gt; contents)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.contents = contents;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">apply</span><span class="params">(DynamicContext context)</span> </span>&#123;</span><br><span class="line">        contents.forEach(node -&gt; node.apply(context));</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><code>MixedSqlNode</code>内部有所有的 <code>SqlNode</code>，它的 <code>apply()</code>方法就是遍历节点然后把每个节点的 <code>apply()</code>方法都执行一遍。</p>
<p>这里另外提到一点，对于动态标签里的语法，比如 <code>&lt;if test=&quot;...&quot;&gt;</code>里的 test，这里的表达式实际上是 Ognl 表达式，这在 jsp 中也有使用，我们可以简单理解为一个脚本语言，通过表达式，我们可以实现一些简单的功能，比如取值或者比较等等。</p>
<h3 id="8rawsqlsource与dynamicsqlsource">8.RawSqlSource与DynamicSqlSource</h3>
<p><code>XMLLanguageDriver.createSqlSource()</code>最终会根据<code>textSqlNode.isDynamic()</code>的结果——也就是方法声明的 sql 中是否含有动态标签——区分要创建哪一种 <code>SqlSource</code>，带<code>$&#123;&#125;</code>和动态标签的用<code>DynamicSqlSource</code>，不带的用 <code>RawSqlSource</code>。</p>
<p>我们以比较简单的 <code>RawSqlSource</code> 为例：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RawSqlSource</span> <span class="keyword">implements</span> <span class="title">SqlSource</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> SqlSource sqlSource;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">RawSqlSource</span><span class="params">(Configuration configuration, SqlNode rootSqlNode, Class&lt;?&gt; parameterType)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>(configuration, getSql(configuration, rootSqlNode), parameterType);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">RawSqlSource</span><span class="params">(Configuration configuration, String sql, Class&lt;?&gt; parameterType)</span> </span>&#123;</span><br><span class="line">        SqlSourceBuilder sqlSourceParser = <span class="keyword">new</span> SqlSourceBuilder(configuration);</span><br><span class="line">        Class&lt;?&gt; clazz = parameterType == <span class="keyword">null</span> ? Object.class : parameterType;</span><br><span class="line">        <span class="comment">// 将#&#123;&#125;表达式替换为？占位符</span></span><br><span class="line">        sqlSource = sqlSourceParser.parse(sql, clazz, <span class="keyword">new</span> HashMap&lt;&gt;());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 从上下文中获取根标签，也就是之前提到的MixedSqlNode</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> String <span class="title">getSql</span><span class="params">(Configuration configuration, SqlNode rootSqlNode)</span> </span>&#123;</span><br><span class="line">        DynamicContext context = <span class="keyword">new</span> DynamicContext(configuration, <span class="keyword">null</span>);</span><br><span class="line">        <span class="comment">// 解析处理所有的SqlNode，并拼接到context里</span></span><br><span class="line">        rootSqlNode.apply(context);</span><br><span class="line">        <span class="comment">// 获取拼接好的sql</span></span><br><span class="line">        <span class="keyword">return</span> context.getSql();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> BoundSql <span class="title">getBoundSql</span><span class="params">(Object parameterObject)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 获取最终的BoundSql</span></span><br><span class="line">        <span class="keyword">return</span> sqlSource.getBoundSql(parameterObject);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可以看到，<code>getSql()</code>本质上就是拿到处理好的 sql 节点，然后调用他们的 <code>apply()</code>方法，最终拼接到上下文中，然后再返回这个拼好的 sql。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DynamicSqlSource</span> <span class="keyword">implements</span> <span class="title">SqlSource</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Configuration configuration;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> SqlNode rootSqlNode;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">DynamicSqlSource</span><span class="params">(Configuration configuration, SqlNode rootSqlNode)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.configuration = configuration;</span><br><span class="line">        <span class="keyword">this</span>.rootSqlNode = rootSqlNode;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> BoundSql <span class="title">getBoundSql</span><span class="params">(Object parameterObject)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 获取上下文并解析根标签</span></span><br><span class="line">        DynamicContext context = <span class="keyword">new</span> DynamicContext(configuration, parameterObject);</span><br><span class="line">        rootSqlNode.apply(context);</span><br><span class="line">        SqlSourceBuilder sqlSourceParser = <span class="keyword">new</span> SqlSourceBuilder(configuration);</span><br><span class="line">        Class&lt;?&gt; parameterType = parameterObject == <span class="keyword">null</span> ? Object.class : parameterObject.getClass();</span><br><span class="line">        <span class="comment">// 将#&#123;&#125;表达式替换为？占位符，获取最终的BoundSql</span></span><br><span class="line">        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());</span><br><span class="line">        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);</span><br><span class="line">        <span class="comment">// 添加参数到boundSql中</span></span><br><span class="line">        context.getBindings().forEach(boundSql::setAdditionalParameter);</span><br><span class="line">        <span class="keyword">return</span> boundSql;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><code>DynamicSqlSource</code> 和<code>RawSqlSource</code>没有什么区别，只不过由于存在动态标签与<code>$&#123;&#125;</code>，相比简单的静态 sql，需要先将这两者解析为 sql。</p>
<h3 id="9sqlsourcebuilder">9.SqlSourceBuilder</h3>
<p><code>SqlSourceBuilder.getBoundSql()</code>的主要作用就是将<code>#&#123;&#125;</code>占位符替换为 jdbc 中的<code>?</code>占位符：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> SqlSource <span class="title">parse</span><span class="params">(String originalSql, Class&lt;?&gt; parameterType, Map&lt;String, Object&gt; additionalParameters)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 参数处理器</span></span><br><span class="line">    ParameterMappingTokenHandler handler = <span class="keyword">new</span> ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);</span><br><span class="line">    <span class="comment">// #&#123;&#125;赋值表达式处理器</span></span><br><span class="line">    GenericTokenParser parser = <span class="keyword">new</span> GenericTokenParser(<span class="string">&quot;#&#123;&quot;</span>, <span class="string">&quot;&#125;&quot;</span>, handler);</span><br><span class="line">    String sql;</span><br><span class="line">    <span class="comment">// 如果有额外的空格就先删除</span></span><br><span class="line">    <span class="keyword">if</span> (configuration.isShrinkWhitespacesInSql()) &#123;</span><br><span class="line">        <span class="comment">// 将#&#123;&#125;表达式替换为具体的参数值</span></span><br><span class="line">        sql = parser.parse(removeExtraWhitespaces(originalSql));</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        sql = parser.parse(originalSql);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 转为静态StaticSqlSource</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> StaticSqlSource(configuration, sql, handler.getParameterMappings());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>所有的 sql 最后都会完成解析，变为最终的自带？占位符的静态 sql 字符串，sql 与对应的入参、返回值类型等信息最终被封装为一个对象，这个对象就是 <code>BoundSql</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BoundSql</span> </span>&#123;</span><br><span class="line">    <span class="comment">// sql字符串</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String sql;</span><br><span class="line">    <span class="comment">// 与占位符对应的入参信息</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> List&lt;ParameterMapping&gt; parameterMappings;</span><br><span class="line">    <span class="comment">// 参数</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Object parameterObject;</span><br><span class="line">    <span class="comment">// 附加参数</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, Object&gt; additionalParameters;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> MetaObject metaParameters;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>里面包含有带占位符和动态标签的原始 sql 语句，以及方法声明上相关的入参。</p>
<p>然后我们再回头看看<code>DynamicSqlSource</code>，我们知道由于存在动态标签，因此<code>DynamicSqlSource</code>会在创建了<code>StaticSqlSource</code>之后，通过<code>StaticSqlSource</code>拿到仍然带有动态标签数据的 <code>BoundSql</code>，接着在根据入参处理动态标签，最终再生成一个 <code>BoundSql</code>。</p>
<h2 id="二-总结">二、总结</h2>
<p>我们总结一下 Mapper 中方法声明被变成 sql 加载进 Mybatis 的过程：</p>
<ol>
<li>
<p>方法声明的解析发生在一个 Mapper 文件被加载——也就是 <code>XMLMapperBuilder</code>构建——的时候，此时各个 statement 都还只是未被解析的 XML 节点；</p>
</li>
<li>
<p>在<code>XMLMapperBuilder</code>中，先通过<code>parsePendingStatements()</code>遍历节点，然后依次调用节点的 <code>parseStatementNode()</code>方法，在这一步主要一下几件事：</p>
<ul>
<li>获取 id、超时时间与缓存等相关的配置信息；</li>
<li>获取方法入参与返回值类型；</li>
<li>解析 <code>&lt;include&gt;</code>节点，并替换为相应的内容；</li>
<li>解析 <code>&lt;selectKey&gt;</code>节点，根据配置设置相应的主键生成策略，完成后然后删除节点；</li>
<li>解析sql，根据配置的<code>LanguageDriver</code>将 sql 解析为对应的 <code>SqlSource</code>；</li>
<li>获取 <code>StatementTyoe</code>，默认都为<code>prepared</code>；</li>
</ul>
<p>然后根据以上的信息构建一个 <code>MappedStatement</code>对象，这个 <code>MappedStatement</code>即是后续方法实现的基础。</p>
</li>
</ol>
<p>然后回头再看看步骤二，步骤二干了很多事，但是最重要的在于<strong>解析 statement 语法生成 <code>SqlSource</code></strong>：</p>
<ol>
<li>
<p>获取 <code>LanguageDriver</code> 接口实现类，该实现类为来在配置文件加载时 Mybatis 默认提供的 <code>XMLLanguageDriver</code>，主要用来解析 Myabtis 特有的带有动态标签与参数标签的语法；</p>
</li>
<li>
<p>在 <code>LanguageDriver</code>中创建<code>XMLScriptBuilder</code>用于解析 statement 中的语法，这里如果是类似 <code>@Select</code>这样注解形式的sql，就先解析为 xml 再用<code>XMLScriptBuilder</code>；</p>
</li>
<li>
<p>在<code>XMLScriptBuilder</code>中将 statement 语法也解析为一串的 XML 节点，并根据节点对应的语法做了区分：</p>
<ul>
<li>如果是带有 <code>$&#123;&#125;</code>表达式的字符串节点被认为是动态 sql 节点，转为<code>TextSqlNode</code>，</li>
<li>如果是只带有<code>#&#123;&#125;</code>表达式的字符串节点或者干脆就是纯字符串的字符串节点，转为<code>StaticTextSqlNode</code>，</li>
<li>如果是动态标签，就通过节点解析器<code>NodeHandler</code>处理，最后转为类似 <code>WhereSqlNode</code>这样的动态标签节点；</li>
</ul>
</li>
<li>
<p>还是在<code>XMLScriptBuilder</code>中，解析出来的各种 Node 都是 <code>SqlNode</code> 接口的实现类，他们都有 <code>apply()</code>方法，当调用的时候，会把自身对应的数据解析为 sql 并拼接到一个上下文对象的 sql 字符串中。</p>
<p>最终这些节点都被添加到一个集合中，最终放入一个根节点 <code>MixedSqlNode</code>里，当调用<code>MixedSqlNode</code>的<code>apply()</code>方法时，就会调用所有节点的<code>apply()</code>方法民，最终就会得到一个完整的 sql。</p>
</li>
<li>
<p>回到<code>LanguageDriver</code>中，在它的<code>createSqlSource()</code>方法中，根据是否带有<code>$&#123;&#125;</code>与动态标签区分为<code>DynamicSqlSource</code>与<code>RawSqlSource</code>两种 <code>DataSource</code>。</p>
<ul>
<li><code>RawSqlSource</code>：调用<code>MixedSqlNode.apply()</code>拿到完整的 sql，然后把 <code>#&#123;&#125;</code>表达式替换为 <code>?</code>占位符，接着根据 sql 创建一个<code>StaticSqlSource</code>，然后通过<code>StaticSqlSource</code>获取<code>boundSql</code>。</li>
<li><code>DynamicSqlSource</code>：跟<code>RawSqlSource</code>一样，先获得完整的 sql，并生成一个<code>StaticSqlSource</code>对象，然后通过<code>StaticSqlSource</code>拿到<code>boundSql</code>，接着<strong>再根据传入的参数处理 <code>BoundSql</code>中的动态标签，最终再生成一个 <code>BoundSql</code></strong>。</li>
</ul>
</li>
</ol>

      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <a href="/tags/Mybatis/" rel="tag"># Mybatis</a>
          
        </div>
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2021/06/14/mybatis/Mybatis%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AMapper%E6%8E%A5%E5%8F%A3%E7%9A%84%E7%BB%91%E5%AE%9A/" rel="next" title="Mybatis源码学习（二）：Mapper接口的绑定">
                <i class="fa fa-chevron-left"></i> Mybatis源码学习（二）：Mapper接口的绑定
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/2022/02/18/%E6%9D%82%E4%B8%83%E4%B9%B1%E5%85%AB/%E5%8F%91%E5%B8%83%E8%87%AA%E5%B7%B1%E7%9A%84jar%E5%8C%85%E5%88%B0Maven%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93/" rel="prev" title="发布自己的jar包到Maven官方仓库">
                发布自己的jar包到Maven官方仓库 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <img class="site-author-image" itemprop="image"
                src="/images/Createsequence.jpg"
                alt="" />
            
              <p class="site-author-name" itemprop="name"></p>
              <p class="site-description motion-element" itemprop="description"></p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/%7C%7Carchive">
              
                  <span class="site-state-item-count">89</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">15</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                <a href="/tags/index.html">
                  <span class="site-state-item-count">21</span>
                  <span class="site-state-item-name">标签</span>
                </a>
              </div>
            

          </nav>

          
            <div class="feed-link motion-element">
              <a href="/atom.xml" rel="alternate">
                <i class="fa fa-rss"></i>
                RSS
              </a>
            </div>
          

          

          
          

          
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%A6%82%E8%BF%B0"><span class="nav-text">概述</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B8%80-sql%E8%A7%A3%E6%9E%90"><span class="nav-text">一、sql解析</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1xmlmapperbuilder"><span class="nav-text">1.XMLMapperBuilder</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2parsestatementnode%E6%96%B9%E6%B3%95"><span class="nav-text">2.parseStatementNode方法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3sqlsource"><span class="nav-text">3.SqlSource</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#4languagedriver"><span class="nav-text">4.LanguageDriver</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#5xmllanguagedriver"><span class="nav-text">5.XMLLanguageDriver</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#7xmlscriptbuilder"><span class="nav-text">7.XMLScriptBuilder</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#8sqlnode"><span class="nav-text">8.SqlNode</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#8rawsqlsource%E4%B8%8Edynamicsqlsource"><span class="nav-text">8.RawSqlSource与DynamicSqlSource</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#9sqlsourcebuilder"><span class="nav-text">9.SqlSourceBuilder</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BA%8C-%E6%80%BB%E7%BB%93"><span class="nav-text">二、总结</span></a></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Createsequence</span>

  
</div>






  <div class="theme-info">主题 &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Gemini</a> v5.1.4</div>




        







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
          <span id="scrollpercent"><span>0</span>%</span>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  


  <script type="text/javascript" src="/js/src/affix.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/schemes/pisces.js?v=5.1.4"></script>



  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  












  

  <script type="text/javascript">
    // Popup Window;
    var isfetched = false;
    var isXml = true;
    // Search DB path;
    var search_path = "search.xml";
    if (search_path.length === 0) {
      search_path = "search.xml";
    } else if (/json$/i.test(search_path)) {
      isXml = false;
    }
    var path = "/" + search_path;
    // monitor main search box;

    var onPopupClose = function (e) {
      $('.popup').hide();
      $('#local-search-input').val('');
      $('.search-result-list').remove();
      $('#no-result').remove();
      $(".local-search-pop-overlay").remove();
      $('body').css('overflow', '');
    }

    function proceedsearch() {
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay"></div>')
        .css('overflow', 'hidden');
      $('.search-popup-overlay').click(onPopupClose);
      $('.popup').toggle();
      var $localSearchInput = $('#local-search-input');
      $localSearchInput.attr("autocapitalize", "none");
      $localSearchInput.attr("autocorrect", "off");
      $localSearchInput.focus();
    }

    // search function;
    var searchFunc = function(path, search_id, content_id) {
      'use strict';

      // start loading animation
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay">' +
          '<div id="search-loading-icon">' +
          '<i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>' +
          '</div>' +
          '</div>')
        .css('overflow', 'hidden');
      $("#search-loading-icon").css('margin', '20% auto 0 auto').css('text-align', 'center');

      $.ajax({
        url: path,
        dataType: isXml ? "xml" : "json",
        async: true,
        success: function(res) {
          // get the contents from search data
          isfetched = true;
          $('.popup').detach().appendTo('.header-inner');
          var datas = isXml ? $("entry", res).map(function() {
            return {
              title: $("title", this).text(),
              content: $("content",this).text(),
              url: $("url" , this).text()
            };
          }).get() : res;
          var input = document.getElementById(search_id);
          var resultContent = document.getElementById(content_id);
          var inputEventFunction = function() {
            var searchText = input.value.trim().toLowerCase();
            var keywords = searchText.split(/[\s\-]+/);
            if (keywords.length > 1) {
              keywords.push(searchText);
            }
            var resultItems = [];
            if (searchText.length > 0) {
              // perform local searching
              datas.forEach(function(data) {
                var isMatch = false;
                var hitCount = 0;
                var searchTextCount = 0;
                var title = data.title.trim();
                var titleInLowerCase = title.toLowerCase();
                var content = data.content.trim().replace(/<[^>]+>/g,"");
                var contentInLowerCase = content.toLowerCase();
                var articleUrl = decodeURIComponent(data.url);
                var indexOfTitle = [];
                var indexOfContent = [];
                // only match articles with not empty titles
                if(title != '') {
                  keywords.forEach(function(keyword) {
                    function getIndexByWord(word, text, caseSensitive) {
                      var wordLen = word.length;
                      if (wordLen === 0) {
                        return [];
                      }
                      var startPosition = 0, position = [], index = [];
                      if (!caseSensitive) {
                        text = text.toLowerCase();
                        word = word.toLowerCase();
                      }
                      while ((position = text.indexOf(word, startPosition)) > -1) {
                        index.push({position: position, word: word});
                        startPosition = position + wordLen;
                      }
                      return index;
                    }

                    indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
                    indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
                  });
                  if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
                    isMatch = true;
                    hitCount = indexOfTitle.length + indexOfContent.length;
                  }
                }

                // show search results

                if (isMatch) {
                  // sort index by position of keyword

                  [indexOfTitle, indexOfContent].forEach(function (index) {
                    index.sort(function (itemLeft, itemRight) {
                      if (itemRight.position !== itemLeft.position) {
                        return itemRight.position - itemLeft.position;
                      } else {
                        return itemLeft.word.length - itemRight.word.length;
                      }
                    });
                  });

                  // merge hits into slices

                  function mergeIntoSlice(text, start, end, index) {
                    var item = index[index.length - 1];
                    var position = item.position;
                    var word = item.word;
                    var hits = [];
                    var searchTextCountInSlice = 0;
                    while (position + word.length <= end && index.length != 0) {
                      if (word === searchText) {
                        searchTextCountInSlice++;
                      }
                      hits.push({position: position, length: word.length});
                      var wordEnd = position + word.length;

                      // move to next position of hit

                      index.pop();
                      while (index.length != 0) {
                        item = index[index.length - 1];
                        position = item.position;
                        word = item.word;
                        if (wordEnd > position) {
                          index.pop();
                        } else {
                          break;
                        }
                      }
                    }
                    searchTextCount += searchTextCountInSlice;
                    return {
                      hits: hits,
                      start: start,
                      end: end,
                      searchTextCount: searchTextCountInSlice
                    };
                  }

                  var slicesOfTitle = [];
                  if (indexOfTitle.length != 0) {
                    slicesOfTitle.push(mergeIntoSlice(title, 0, title.length, indexOfTitle));
                  }

                  var slicesOfContent = [];
                  while (indexOfContent.length != 0) {
                    var item = indexOfContent[indexOfContent.length - 1];
                    var position = item.position;
                    var word = item.word;
                    // cut out 100 characters
                    var start = position - 20;
                    var end = position + 80;
                    if(start < 0){
                      start = 0;
                    }
                    if (end < position + word.length) {
                      end = position + word.length;
                    }
                    if(end > content.length){
                      end = content.length;
                    }
                    slicesOfContent.push(mergeIntoSlice(content, start, end, indexOfContent));
                  }

                  // sort slices in content by search text's count and hits' count

                  slicesOfContent.sort(function (sliceLeft, sliceRight) {
                    if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
                      return sliceRight.searchTextCount - sliceLeft.searchTextCount;
                    } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
                      return sliceRight.hits.length - sliceLeft.hits.length;
                    } else {
                      return sliceLeft.start - sliceRight.start;
                    }
                  });

                  // select top N slices in content

                  var upperBound = parseInt('1');
                  if (upperBound >= 0) {
                    slicesOfContent = slicesOfContent.slice(0, upperBound);
                  }

                  // highlight title and content

                  function highlightKeyword(text, slice) {
                    var result = '';
                    var prevEnd = slice.start;
                    slice.hits.forEach(function (hit) {
                      result += text.substring(prevEnd, hit.position);
                      var end = hit.position + hit.length;
                      result += '<b class="search-keyword">' + text.substring(hit.position, end) + '</b>';
                      prevEnd = end;
                    });
                    result += text.substring(prevEnd, slice.end);
                    return result;
                  }

                  var resultItem = '';

                  if (slicesOfTitle.length != 0) {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + highlightKeyword(title, slicesOfTitle[0]) + "</a>";
                  } else {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + title + "</a>";
                  }

                  slicesOfContent.forEach(function (slice) {
                    resultItem += "<a href='" + articleUrl + "'>" +
                      "<p class=\"search-result\">" + highlightKeyword(content, slice) +
                      "...</p>" + "</a>";
                  });

                  resultItem += "</li>";
                  resultItems.push({
                    item: resultItem,
                    searchTextCount: searchTextCount,
                    hitCount: hitCount,
                    id: resultItems.length
                  });
                }
              })
            };
            if (keywords.length === 1 && keywords[0] === "") {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-search fa-5x" /></div>'
            } else if (resultItems.length === 0) {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-frown-o fa-5x" /></div>'
            } else {
              resultItems.sort(function (resultLeft, resultRight) {
                if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
                  return resultRight.searchTextCount - resultLeft.searchTextCount;
                } else if (resultLeft.hitCount !== resultRight.hitCount) {
                  return resultRight.hitCount - resultLeft.hitCount;
                } else {
                  return resultRight.id - resultLeft.id;
                }
              });
              var searchResultList = '<ul class=\"search-result-list\">';
              resultItems.forEach(function (result) {
                searchResultList += result.item;
              })
              searchResultList += "</ul>";
              resultContent.innerHTML = searchResultList;
            }
          }

          if ('auto' === 'auto') {
            input.addEventListener('input', inputEventFunction);
          } else {
            $('.search-icon').click(inputEventFunction);
            input.addEventListener('keypress', function (event) {
              if (event.keyCode === 13) {
                inputEventFunction();
              }
            });
          }

          // remove loading animation
          $(".local-search-pop-overlay").remove();
          $('body').css('overflow', '');

          proceedsearch();
        }
      });
    }

    // handle and trigger popup window;
    $('.popup-trigger').click(function(e) {
      e.stopPropagation();
      if (isfetched === false) {
        searchFunc(path, 'local-search-input', 'local-search-result');
      } else {
        proceedsearch();
      };
    });

    $('.popup-btn-close').click(onPopupClose);
    $('.popup').click(function(e){
      e.stopPropagation();
    });
    $(document).on('keyup', function (event) {
      var shouldDismissSearchPopup = event.which === 27 &&
        $('.search-popup').is(':visible');
      if (shouldDismissSearchPopup) {
        onPopupClose();
      }
    });
  </script>





  

  

  

  
  

  

  

  


  <!-- 引入目录截取js -->
  <script type="text/javascript" src="/js/src/custom/custom.js"></script>
</body>
</html>
